﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Sinacor.Infra.Common.Serialization;
using System.Collections;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Reflection;
using System.Diagnostics;

namespace Sinacor.Infra.UI.Common
{
	[Serializable]
	public class ModelCollection<T> : ObservableCollection<T>, IModelCollection where T : BaseModel
	{
		#region IsClone
		[field: NonSerialized]
		private bool _isClone;
		public bool IsClone
		{
			get { return _isClone; }
			set { _isClone = value; }
		}
		#endregion

		#region Construtor
		public ModelCollection()
			: base()
		{
			this.Initialize(false);
		}
		public ModelCollection(IEnumerable list)
			: base(list.Cast<T>().ToList())
		{
			this.Initialize(false);
		}

		public ModelCollection(IEnumerable list, bool enableStateControl)
			: base(list.Cast<T>().ToList())
		{
			this.Initialize(enableStateControl);
		}

		protected void Initialize(bool enableStateControl)
		{
			this._changedItems = new ItemStateCollection<T>();
			this.ChangedItems.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ChangedItems_CollectionChanged);

			this.EnableStateControl = enableStateControl;

			((IObservableObject)this).ParentObjects = new List<IObservableObject>();

			// IObservableObject
			this.IsObserverStarted = false;
			this.SpreadChanges = true;
			this.InternalPropertyChanged += new PropertyChangedEventHandler(ObservableObject_InternalPropertyChanged);
			this.ObserverStarted += new Action(ObservableObject_ObserverStarted);
			this.ObserverStoped += new Action(ObservableObject_ObserverStoped);
			this.ChangesCleaned += new Action(ObservableObject_ChangesCleaned);
			this.SpreadChangesChanged += new Action<bool>(ModelCollection_SpreadChangesChanged);
		}

		private void ChangedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
		{
			this.HasChanges = (this.EnableStateControl && this.ChangedItems.Count > 0);
		}
		#endregion

		#region ParentObjects
		//Collection que armazena a referencia para os "pais" do objeto
		[field: NonSerialized]
		private List<IObservableObject> _parentObjects;
		List<IObservableObject> IObservableObject.ParentObjects
		{
			get
			{
				if (_parentObjects == null)
					_parentObjects = new List<IObservableObject>();

				return _parentObjects;
			}
			set { _parentObjects = value; }
		}
		#endregion

		#region EnableStateControl
		private bool _enableStateControl = false;
		/// <summary>
		/// Indica se a coleção vai controlar o estado de alteração dos itens
		/// </summary>
		public bool EnableStateControl
		{
			get { return _enableStateControl; }
			set
			{
				_enableStateControl = value;

				this.OnPropertyChanged(new PropertyChangedEventArgs("EnableStateControl"));

				if (!value && this.HasChanges)
					UndoAll();
			}
		}
		#endregion

		#region HasChanges
		private bool _hasChanges = false;
		/// <summary>
		/// Indica se a coleção vai controlar o estado de alteração dos itens
		/// </summary>
		public bool HasChanges
		{
			get { return _hasChanges; }
			protected set { _hasChanges = value; this.OnPropertyChanged(new PropertyChangedEventArgs("HasChanges")); }
		}
		#endregion

		#region
		//Armazena os objetos cuja os eventos ja foram assinados
		//Utilizado para resolver o problema de performance relacionado a chamada do método
		//DetachEvents no metodo AttachEvents
		private Hashtable AttachedObjects = new Hashtable();
		#endregion

		#region ChangedItems
		private ItemStateCollection<T> _changedItems;
		/// <summary>
		/// Contém os itens alterados do grid
		/// </summary>
		internal ItemStateCollection<T> ChangedItems
		{
			get { return _changedItems; }
		}
		#endregion

		#region InsertItem
		protected override void InsertItem(int index, T item)
		{
			if (this.EnableStateControl)
			{
				this.ChangedItems.AddInserted(index);
				item.State = State.Inserted;
			}

			if (item is IObservableObject)
			{
				if (!((IObservableObject)this).ParentObjects.Contains(item as IObservableObject))
				{
					this.AttachObservableObject((IObservableObject)item);
				}
			}

			base.InsertItem(index, item);
		}
		#endregion

		#region SetItem
		protected override void SetItem(int index, T item)
		{
			if (this.EnableStateControl)
			{
				T clonedItem = CloneHelper.Clone(this[index]) as T;
				ChangedItems.AddUpdated(index, clonedItem);

				if (this[index].State != State.Inserted)
					item.State = State.Updated;
				else if (this[index].State == State.Inserted)
					item.State = State.Inserted;

			}


			if (item is IObservableObject)
			{
				if (!(item as IObservableObject).ParentObjects.Contains(this))
					if (!CheckCyclicReference(this, item as IObservableObject))
						this.AttachObservableObject((IObservableObject)item);

			}

			base.SetItem(index, item);
		}
		#endregion

		#region RemoveItem
		protected override void RemoveItem(int index)
		{
			if (this.EnableStateControl)
			{
				this.ChangedItems.AddDeleted(index);
				this[index].State = State.Deleted;
			}
			else
			{
				if (this[index] is IObservableObject)
					this.DetachObservableObject((IObservableObject)this[index]);

				base.RemoveItem(index);
			}

		}
		#endregion

		#region ClearItems
		protected override void ClearItems()
		{
			if (this.EnableStateControl)
			{
				this.ChangedItems.Clear();
			}

			foreach (object item in this)
			{
				if (item is IObservableObject)
					this.DetachObservableObject((IObservableObject)item);
			}

			base.ClearItems();
		}
		#endregion

		#region DeleteItem
		private void DeleteItem(int index)
		{
			if (this[index] is IObservableObject)
				this.DetachObservableObject((IObservableObject)this[index]);

			base.RemoveItem(index);

			foreach (ItemState<T> item in this.ChangedItems)
			{
				if (item.Index > index)
					item.Index = item.Index - 1;
			}
		}
		#endregion

		#region GetInserted
		/// <summary>
		/// Retorna todos os itens incluídos
		/// </summary>
		/// <returns>Coleção com os itens incluídos</returns>
		public ModelCollection<T> GetInserted()
		{
			ModelCollection<T> items = new ModelCollection<T>();

			// Capturando todos os itens que a primeira ação é Inserted
			List<ItemState<T>> inseridos = this.ChangedItems.GetFirstActions().FindAll(
																			   delegate(ItemState<T> target)
																			   {
																				   return (target.Status == State.Inserted);
																			   }
																			);
			// Percorrendo itens inseridos
			foreach (ItemState<T> itemState in inseridos)
			{
				// Verifica se a última ação no item não foi Deleted
				if (this.ChangedItems.FindLastByIndex(itemState.Index).Status != State.Deleted)
				{
					// Adiciona item na coleção de retorno
					items.Add(this[itemState.Index]);
				}
			}

			return items;
		}
		#endregion

		#region GetUpdated
		/// <summary>
		/// Retorna todos os itens alterados
		/// </summary>
		/// <returns>Coleção com os itens incluídos</returns>
		public ModelCollection<T> GetUpdated()
		{
			ModelCollection<T> items = new ModelCollection<T>();

			// Capturando todos os itens que a última ação é Updated
			List<ItemState<T>> alterados = this.ChangedItems.GetLastActions().FindAll(
																			   delegate(ItemState<T> target)
																			   {
																				   return (target.Status == State.Updated);
																			   }
																		   );
			// Percorrendo itens alterados
			foreach (ItemState<T> itemState in alterados)
			{
				// Verifica se a primeira ação no item não foi Inserted
				if (this.ChangedItems.FindByIndex(itemState.Index).Status != State.Inserted)
				{
					// Adiciona item na coleção de retorno
					items.Add(this[itemState.Index]);
				}
			}

			return items;

		}
		#endregion

		#region GetDeleted
		/// <summary>
		/// Retorna todos os itens excluídos
		/// </summary>
		/// <returns>Coleção com os itens incluídos</returns>
		public ModelCollection<T> GetDeleted()
		{
			ModelCollection<T> items = new ModelCollection<T>();

			// Capturando todos os itens que a última ação é Deleted
			List<ItemState<T>> excluidos = this.ChangedItems.GetLastActions().FindAll(
																			   delegate(ItemState<T> target)
																			   {
																				   return (target.Status == State.Deleted);
																			   }
																		   );
			// Percorrendo itens excluídos
			foreach (ItemState<T> itemState in excluidos)
			{
				// Verifica se a primeira ação no item não foi Inserted
				if (this.ChangedItems.FindByIndex(itemState.Index).Status != State.Inserted)
				{
					// Adiciona item na coleção de retorno
					items.Add(this[itemState.Index]);
				}
			}

			return items;

		}
		#endregion

		#region GetChanged
		/// <summary>
		/// Retorna todos os itens modificados
		/// </summary>
		/// <returns>Coleção com os itens incluídos</returns>
		public ModelCollection<T> GetChanged()
		{
			ModelCollection<T> changed = new ModelCollection<T>();

			changed = this.GetInserted();
			changed = new ModelCollection<T>(changed.Concat(this.GetUpdated()));
			changed = new ModelCollection<T>(changed.Concat(this.GetDeleted()));

			return changed;
		}
		#endregion

		#region GetLastChanged
		/// <summary>
		/// Captura o último item alterado
		/// </summary>
		/// <returns>
		/// Item alterado ou null se não houve alterações
		/// </returns>
		public T GetLastChanged()
		{
			if (ChangedItems.Count > 0)
			{
				ItemState<T> item = this.ChangedItems[ChangedItems.Count - 1];
				return this[item.Index];
			}

			return null;
		}
		#endregion

		#region Save
        /// <summary>
        /// Persiste a edição do item
        /// </summary>
        /// <param name="item">Item que será persistido</param>
        /// <returns>
        /// true se persistiu com sucesso, false se o item não foi modificado
        /// </returns>
        public bool Save(T item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            return this.Save(this.IndexOf(item));
        }

		/// <summary>
		/// Persiste a edição do item indicado pelo índice
		/// </summary>
		/// <param name="index">Índice do item</param>
		/// <returns>
		/// true se persistiu com sucesso, false se o item não foi modificado
		/// </returns>
		public bool Save(int index)
		{
			bool retorno = false;

			ReadOnlyCollection<ItemState<T>> itemCollection = ChangedItems.FindAllByIndex(index);

			//CASO EXITAM AÇÕES PARA REALIZAR o SAVE
			if (itemCollection.Count > 0)
			{
				//PEGA O PRIMEIRO E O ULTIMO ESTADO ARMAZENADO NA COLEÇÃO DE MODIFICAÇÕES
				ItemState<T> primeiroItem = itemCollection[0];
				ItemState<T> ultimoItem = itemCollection[itemCollection.Count - 1];

				// PRIMEIRO INSERTED - ULTIMO INSERTED OU UPDATED (disparar evento de insert mas com dados do ultimo)
				if (primeiroItem.Status == State.Inserted && (ultimoItem.Status == State.Inserted || ultimoItem.Status == State.Updated))
				{
					ChangedItems.RemoveByIndex(index);
					this[index].State = State.Unchanged;

					retorno = true;
				}

				// PRIMEIRO INSERTED - ULTIMO DELETED (limpa as coleções e não dispara evento)
				if (primeiroItem.Status == State.Inserted && ultimoItem.Status == State.Deleted)
				{
					ChangedItems.RemoveByIndex(index);
					this.DeleteItem(index);

					retorno = true;
				}

				// PRIMEIRO UPDATED - ULTIMO DELETED (disparar evento de delete com dados do último)
				if (primeiroItem.Status == State.Updated && ultimoItem.Status == State.Deleted)
				{
					ChangedItems.RemoveByIndex(index);
					this.DeleteItem(index);

					retorno = true;
				}

				// PRIMEIRO UPDATED - ULTIMO UPDATED (disparar evento de update com dados do último)
				if (primeiroItem.Status == State.Updated && ultimoItem.Status == State.Updated)
				{
					ChangedItems.RemoveByIndex(index);
					this[index].State = State.Unchanged;

					retorno = true;
				}

				// PRIMEIRO DELETED - ULTIMO UPDATED (disparar evento de update com dados do último)
				if (primeiroItem.Status == State.Deleted && ultimoItem.Status == State.Updated)
				{
					ChangedItems.RemoveByIndex(index);
					this[index].State = State.Unchanged;

					retorno = true;
				}

				// PRIMEIRO DELETED - ULTIMO DELETED (disparar evento de delete com dados do último)
				if (primeiroItem.Status == State.Deleted && ultimoItem.Status == State.Deleted)
				{
					ChangedItems.RemoveByIndex(index);
					this.DeleteItem(index);

					retorno = true;
				}

			}

			return retorno;
		}

		/// <summary>
		/// Persiste a edição dos itens indicados pelos índices
		/// </summary>
		/// <param name="indexs">Índices dos itens</param>
		/// <returns>
		/// true se persistiu com sucesso, false se os itens não foram modificados
		/// </returns>
		public bool Save(int[] indexs)
		{
			bool retorno = false;

			if (indexs == null)
			{
				throw new ArgumentNullException("indexs");
			}

			if (indexs.Length > 0)
			{
				retorno = true;

				// Ordenado os indices em ordem decrescente
				Array.Sort<int>(indexs, delegate(int v1, int v2) { return v2.CompareTo(v1); });

				for (int i = 0; i < indexs.Length; i++)
				{
					if (!this.Save(indexs[i]))
					{
						retorno = false;
					}
				}

			}

			return retorno;

		}
		#endregion

		#region Save All
		/// <summary>
		/// Persiste a edição de todos os itens
		/// </summary>
		/// <returns>
		/// true se persistiu com sucesso, false se nenhum item foi modificado
		/// </returns>
		public bool SaveAll()
		{
			if (this.ChangedItems.Count > 0)
			{
				//REMOVE OS ITENS DELETADOS
				List<ItemState<T>> itensDeletados = ChangedItems.GetLastActions(State.Deleted);

				if (itensDeletados.Count > 0)
				{
					foreach (ItemState<T> item in itensDeletados)
					{
						//TODO: PRECISA DELETAR DA COLEÇÃO E REFAZER INDICES
						this.DeleteItem(item.Index);
					}
				}

				ChangedItems.Clear();

				foreach (T item in this)
				{
					item.State = State.Unchanged;
				}

				return true;
			}

			return false;

		}
		#endregion

		#region Undo
        /// <summary>
		/// Desfaz a edição do item
		/// </summary>
        /// <param name="item">Item que será desfeito</param>
		/// <returns>
		/// true se desfez com sucesso, false se o item não foi modificado
		/// </returns>
        public bool Undo(T item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            return this.Undo(this.IndexOf(item));
        }

		/// <summary>
		/// Desfaz a edição do item indicado pelo índice
		/// </summary>
		/// <param name="index">Índice do item</param>
		/// <returns>
		/// true se desfez com sucesso, false se o item não foi modificado
		/// </returns>
		public bool Undo(int index)
		{
			bool retorno = false;

			ReadOnlyCollection<ItemState<T>> itemCollection = ChangedItems.FindAllByIndex(index);

			//CASO EXITAM AÇÕES PARA REALIZAR O UNDO
			if (itemCollection.Count > 0)
			{
				//PEGA O ULTIMO ESTADO ARMAZENADO NA COLEÇÃO DE MODIFICAÇÕES
				ItemState<T> item = itemCollection[itemCollection.Count - 1];

				//VERIFICA O STATUS PARA DEFINIR AS AÇÕES A SEREM TOMADAS COM AS COLEÇÕES
				switch (item.Status)
				{
					case State.Inserted:

						ChangedItems.RemoveByIndex(item.Index);
						this.DeleteItem(item.Index);

						break;

					case State.Updated:

						base.SetItem(item.Index, item.OriginalItem);
						ChangedItems.Remove(item);

						break;

					case State.Deleted:

						ChangedItems.Remove(item);

						State state = State.Unchanged;

						if (itemCollection.Count >= 2)
							state = itemCollection[itemCollection.Count - 2].Status;

						this[item.Index].State = state;

						break;
				}

				retorno = true;
			}

			return retorno;

		}

		/// <summary>
		/// Desfaz a edição dos itens indicados pelos índices
		/// </summary>
		/// <param name="indexs">Índices dos itens</param>
		/// <returns>
		/// true se desfez com sucesso, false se os itens não foram modificados
		/// </returns>
		public bool Undo(int[] indexs)
		{
			if (indexs == null)
			{
				throw new ArgumentNullException("indexs");
			}

			bool retorno = false;

			if (indexs.Length > 0)
			{
				retorno = true;

				// Ordenado os indices em ordem decrescente
				Array.Sort<int>(indexs, delegate(int v1, int v2) { return v2.CompareTo(v1); });

				for (int i = 0; i < indexs.Length; i++)
				{
					if (!this.Undo(indexs[i]))
					{
						retorno = false;
					}
				}
			}

			return retorno;

		}
		#endregion

		#region UndoLast
		/// <summary>
		/// Desfaz a útima edição dos itens
		/// </summary>
		/// <returns>
		/// true se desfez com sucesso, false se os itens não foram modificados
		/// </returns>
		public bool UndoLast()
		{
			if (ChangedItems.Count > 0)
			{
				ItemState<T> item = this.ChangedItems[ChangedItems.Count - 1];
				return Undo(item.Index);
			}

			return true;

		}
		#endregion

		#region UndoAll
		/// <summary>
		/// Desfaz a edição de todos os itens
		/// </summary>
		/// <returns>
		/// true se desfez com sucesso, false se nenhum item foi modificado
		/// </returns>
		public bool UndoAll()
		{
			bool retorno = false;

			//CASO EXITAM AÇÕES PARA REALIZAR O UNDO ALL
			if (this.ChangedItems.Count > 0)
			{
				//SEPARA TODAS AS PRIMEIRAS AÇÕES DE CADA OBJETO ALTERADO
				List<ItemState<T>> primeirasAcoesCollection = this.ChangedItems.GetFirstActions();

				if (primeirasAcoesCollection.Count > 0)
				{
					foreach (ItemState<T> item in primeirasAcoesCollection)
					{
						//VERIFICA O STATUS PARA DEFINIR AS AÇÕES A SEREM TOMADAS COM AS COLEÇÕES
						switch (item.Status)
						{
							case State.Inserted:

								this.DeleteItem(item.Index);

								break;
							case State.Updated:

								base.SetItem(item.Index, item.OriginalItem);
								this.ChangedItems.Remove(item);

								break;
							case State.Deleted:

								// Captura o item original
								T originalItem = this.ChangedItems.SearchFirstUpdatedItem(item.Index);

								if (originalItem != null)
									base.SetItem(item.Index, originalItem);

								this.ChangedItems.Remove(item);

								this[item.Index].State = State.Unchanged;
								break;
						}

					}

					this.ChangedItems.Clear();

				}

				retorno = true;

			}

			return retorno;

		}
		#endregion

		#region IModelCollection<BaseModel> Members

		bool IModelCollection<BaseModel>.EnableStateControl
		{
			get { return this.EnableStateControl; }
			set { this.EnableStateControl = value; }
		}

		ModelCollection<BaseModel> IModelCollection<BaseModel>.GetDeleted()
		{
			return new ModelCollection<BaseModel>(this.GetDeleted());
		}

		ModelCollection<BaseModel> IModelCollection<BaseModel>.GetInserted()
		{
			return new ModelCollection<BaseModel>(this.GetInserted());
		}

		ModelCollection<BaseModel> IModelCollection<BaseModel>.GetUpdated()
		{
			return new ModelCollection<BaseModel>(this.GetUpdated());
		}

		ModelCollection<BaseModel> IModelCollection<BaseModel>.GetChanged()
		{
			return new ModelCollection<BaseModel>(this.GetChanged());
		}

		BaseModel IModelCollection<BaseModel>.GetLastChanged()
		{
			return (BaseModel)this.GetLastChanged();
		}

		bool IModelCollection<BaseModel>.HasChanges
		{
			get { return this.HasChanges; }
		}

		bool IModelCollection<BaseModel>.Save(int[] indexs)
		{
			return this.Save(indexs);
		}

		bool IModelCollection<BaseModel>.Save(int index)
		{
			return this.Save(index);
		}

		bool IModelCollection<BaseModel>.SaveAll()
		{
			return this.SaveAll();
		}

		bool IModelCollection<BaseModel>.Undo(int[] indexs)
		{
			return this.Undo(indexs);
		}

		bool IModelCollection<BaseModel>.Undo(int index)
		{
			return this.Undo(index);
		}

		bool IModelCollection<BaseModel>.UndoAll()
		{
			return this.UndoAll();
		}

		bool IModelCollection<BaseModel>.UndoLast()
		{
			return this.UndoLast();
		}

		#endregion

		#region IObservableObject Members

		#region IsObserverStarted
		/// <summary>
		/// Indica se está monitorando mudanças nas propriedades
		/// </summary>
		public bool IsObserverStarted
		{
			get;
			protected set;
		}
		#endregion

		#region HasPropertyChanges
		private bool _hasPropertyChanges;
		/// <summary>
		/// Indica se houve mudanças nas propriedades
		/// </summary>
		public bool HasPropertyChanges
		{
			get { return _hasPropertyChanges; }
			protected set
			{
				bool temp = _hasPropertyChanges;
				_hasPropertyChanges = value;
				if (temp != _hasPropertyChanges)
				{
					base.OnPropertyChanged(new PropertyChangedEventArgs("HasPropertyChanges"));
				}
				//SetValue("HasPropertyChanges", ref _hasPropertyChanges, value);
			}
		}
		#endregion

		#region SpreadChanges
		private bool _spreadChanges;
		/// <summary>
		/// Indica se as mudanças devem ser propagadas para os objetos pais
		/// </summary>        
		public bool SpreadChanges
		{
			get { return _spreadChanges; }
			set
			{
				bool temp = _spreadChanges;
				_spreadChanges = value;

				if (temp != value)
				{
					if (this.SpreadChangesChanged != null)
						this.SpreadChangesChanged(value);
				}
			}
		}
		#endregion

		#region OnDeserialized
		[OnDeserialized]
		private void OnDeserialized(StreamingContext context)
		{
			this.IsClone = true;
		}
		#endregion

		#region StartObserver
		[field: NonSerialized]
		protected internal event Action ObserverStarted;

		/// <summary>
		/// Ativa o observador de alterações do objeto
		/// </summary>
		public void StartObserver()
		{
			if (this.IsClone)
			{
				this.ReassignInternalEvents();
			}

			if (this.ObserverStarted != null)
				this.ObserverStarted();
		}

		private void ObservableObject_ObserverStarted()
		{
			this.IsObserverStarted = true;
		}

		private void ReassignInternalEvents()
		{
			this.InternalPropertyChanged += new PropertyChangedEventHandler(ObservableObject_InternalPropertyChanged);
			this.ObserverStarted += new Action(ObservableObject_ObserverStarted);
			this.ObserverStoped += new Action(ObservableObject_ObserverStoped);
			this.ChangesCleaned += new Action(ObservableObject_ChangesCleaned);
			this.SpreadChangesChanged += new Action<bool>(ModelCollection_SpreadChangesChanged);

			foreach (IObservableObject obj in this.Items)
			{
				if (obj != null)
					if (!CheckCyclicReference(this, obj))
						this.AttachEvents(obj);
			}
		}
		#endregion

		#region StopObserver
		[field: NonSerialized]
		protected internal event Action ObserverStoped;

		/// <summary>
		/// Desativa o observador de alterações
		/// </summary>
		public void StopObserver()
		{
			if (this.ObserverStoped != null)
				this.ObserverStoped();
		}

		private void ObservableObject_ObserverStoped()
		{
			this.HasPropertyChanges = false;
			this.IsObserverStarted = false;
		}
		#endregion

		#region ClearChanges
		[field: NonSerialized]
		protected internal event Action ChangesCleaned;

		/// <summary>
		/// Limpa as mudanças do objeto
		/// </summary>
		public void ClearChanges()
		{
			if (this.ChangesCleaned != null)
				this.ChangesCleaned();
		}

		private void ObservableObject_ChangesCleaned()
		{
			this.HasPropertyChanges = false;
		}
		#endregion

		#region Observer Core
		/// <summary>
		/// Receber a notificacao de que o objeto foi alterado e mudar seu estado
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void ObservableObject_InternalPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (this.IsObserverStarted && e.PropertyName != "HasPropertyChanges")
			{
				if (this.SpreadChanges || (!this.SpreadChanges && sender == this))
					this.HasPropertyChanges = true;

				if (sender != this)
					this.OnPropertyChanged(e.PropertyName);
				//base.OnPropertyChanged(e);
			}
		}

		/// <summary>
		/// Verificar se houve alteracoes
		/// </summary>
		/// <param name="propName"></param>
		/// <param name="field"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		private bool CheckChanges(object field, object value)
		{
			bool hasChanges = false;

			if (field is IComparable || value is IComparable)
			{
				if (field == null && value == null)
					hasChanges = false;
				else if (field != null)
					hasChanges = (((IComparable)field).CompareTo(value) != 0);
				else if (value != null)
					hasChanges = (((IComparable)value).CompareTo(field) != 0);
				else
					hasChanges = (((IComparable)field).CompareTo(value) != 0);
			}
			else
				hasChanges = (field != value);

			return hasChanges;
		}

		/// <summary>
		/// Atribuir um valor a um field e notificar se ha alteracoes quando o observador estiver ativo
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="propName"></param>
		/// <param name="field"></param>
		/// <param name="value"></param>
		public void SetValue<TValue>(string propName, ref TValue field, TValue value)
		{
			object originalFieldValue = field;
			field = value;

			//caso seja uma sub-model e seja um observable object
			if (field is IObservableObject)
			{
				if (!(field as IObservableObject).ParentObjects.Contains(this))
					if (!CheckCyclicReference(this, field as IObservableObject))
						this.AttachObservableObject((IObservableObject)field);
			}

			// Se valor antigo for um IObservableObject e valor mudou, deve 
			// desatachar o valor antigo
			if (originalFieldValue is IObservableObject &&
				CheckChanges(originalFieldValue, value))
			{
				this.DetachObservableObject((IObservableObject)originalFieldValue);
			}

			//verificar se houve alteracoes de disparar o evento
			if (CheckChanges(originalFieldValue, value))
			{
				if (this.IsObserverStarted)
					this.HasPropertyChanges = true;

				this.OnPropertyChanged(propName);
			}
		}

		/// <summary>
		/// Verifica se ha referencia circular
		/// </summary>
		/// <param name="field"></param>
		/// <returns>
		/// True  - há referência circular
		/// False - não há referência circular
		/// </returns>
		protected static bool CheckCyclicReference(IObservableObject parent, IObservableObject child)
		{
			if (parent.ParentObjects.Contains(child as IObservableObject))
				return true;
			else
			{
				foreach (IObservableObject o in parent.ParentObjects)
				{
					if (CheckCyclicReference(o, child))
						return true;
				}
			}

			return false;
		}

		private void AttachObservableObject(IObservableObject obj)
		{
			//Verificar se o objeto já teve os eventos assinados
			if (!AttachedObjects.Contains(obj.GetHashCode()))
			{
				//Adiciona o objeto a colecao de objetos "atachados"
				AttachedObjects.Add(obj.GetHashCode(), obj);

				AttachEvents(obj);

				if (this.IsObserverStarted)
					obj.StartObserver();
				else
					obj.StopObserver();

				obj.SpreadChanges = this.SpreadChanges;

				if (this.IsObserverStarted && this.SpreadChanges && obj.HasPropertyChanges)
				{
					this.HasPropertyChanges = true;
					this.OnPropertyChanged("");
				}
			}
		}

		private void AttachEvents(IObservableObject obj)
		{

			//adicionar o objeto a lista de "pais" do objeto sendo "atachado"
			if (!obj.ParentObjects.Contains(this))
				obj.ParentObjects.Add(this);

			//assinar os eventos de start e stop do observador de forma a propragar a
			//configuracao para as submodels
			this.ObserverStarted += new Action(obj.StartObserver);
			this.ObserverStoped += new Action(obj.StopObserver);
			this.ChangesCleaned += new Action(obj.ClearChanges);

			this.SpreadChangesChanged += new Action<bool>(obj.OnSpreadChangesChanged);

			obj.InternalPropertyChanged += new PropertyChangedEventHandler(this.ObservableObject_InternalPropertyChanged);
		}

		private void DetachObservableObject(IObservableObject obj)
		{
			//Remove o objeto da lista de "atachados"
			AttachedObjects.Remove(obj.GetHashCode());
			DetachEvents(obj);
		}

		private void DetachEvents(IObservableObject obj)
		{
			//remover o objeto a lista de "pais" do objeto sendo "atachado"
			if (obj.ParentObjects.Contains(this))
				obj.ParentObjects.Remove(this);

			////assinar os eventos de start e stop do observador de forma a propragar a
			////configuracao para as submodels
			if (this.ObserverStarted != null)
				this.ObserverStarted -= new Action(obj.StartObserver);

			if (this.ObserverStoped != null)
				this.ObserverStoped -= new Action(obj.StopObserver);

			if (this.SpreadChangesChanged != null)
				this.SpreadChangesChanged -= new Action<bool>(obj.OnSpreadChangesChanged);

			obj.InternalPropertyChanged -= new PropertyChangedEventHandler(this.ObservableObject_InternalPropertyChanged);
		}

		void IObservableObject.OnSpreadChangesChanged(bool newValue)
		{
			if (this.SpreadChangesChanged != null)
				this.SpreadChangesChanged(newValue);
		}

		void ModelCollection_SpreadChangesChanged(bool newValue)
		{
			this.SpreadChanges = newValue;
		}

		[field: NonSerialized]
		public event PropertyChangedEventHandler InternalPropertyChanged;

		[field: NonSerialized]
		public event Action<bool> SpreadChangesChanged;
		#endregion

		#endregion

		#region INotifyPropertyChanged Members
		/// <summary>
		/// Dispara o evento <c>PropertyChanged</c>
		/// </summary>
		/// <param name="propertyName">Nome da property</param>
		protected virtual void OnPropertyChanged(string propertyName)
		{
			if (this.InternalPropertyChanged != null)
				this.InternalPropertyChanged(this, new PropertyChangedEventArgs(propertyName));

			base.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
		}
		#endregion
	}
}